﻿using Core.Interface;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;

namespace ModBus.Mode
{
    public class ModBusRTU : ModBusDevice
    {
        public RTUParam Param { get; set; }=new RTUParam();

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private SerialPort serialPort;
        byte[] m_RecvBuffer;
        private readonly object rwLock = new object();
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);
        List<byte> m_SendBuffer = new List<byte>();

        public ModBusRTU(string name)
        {
            Name = name;              
        }

        public override bool Connect()
        {
            try
            {
                //在界面上断开再重新连接需要new SerialPort()
                serialPort = new SerialPort();
                if (Param == null) return false;
                serialPort.PortName = Param.PortName;
                serialPort.BaudRate = Param.BaudRate;
                serialPort.DataBits = Param.DataBit;
                serialPort.StopBits = Param.StopBit;
                serialPort.Parity = Param.Parity;
                serialPort.DataReceived += SerialPort_DataReceived;
                serialPort.Open();
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }       
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                m_RecvBuffer = new byte[serialPort.BytesToRead];
                if (serialPort.BytesToRead == serialPort.ReceivedBytesThreshold)
                {
                    serialPort.Read(m_RecvBuffer, 0, m_RecvBuffer.Length);
                    autoResetEvent.Set();
                }
            }
            catch (Exception ex)
            {
                //NotifyG.Error(ex.ToString());
            }
        }

        public override void Close()
        {
            try
            {
                serialPort?.Close();
                m_RecvBuffer = null;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }            
        }

        public override bool ReadBit(ushort startAddress, ushort size, out bool[] values)
        {
            lock (rwLock)
            {
                try
                {
                    bool result = false;
                    values = new bool[size];
                    m_SendBuffer?.Clear();

                    m_SendBuffer.Add(0x01);//单元标识
                    m_SendBuffer.Add(0x01);//功能码

                    byte[] addressBytes = BitConverter.GetBytes(startAddress);
                    Array.Reverse(addressBytes);
                    m_SendBuffer.AddRange(addressBytes);//起始地址

                    byte[] sizeBytes = BitConverter.GetBytes(size);
                    Array.Reverse(sizeBytes);
                    m_SendBuffer.AddRange(sizeBytes);//数量

                    var crcCode = CRCCode(m_SendBuffer.ToArray());
                    m_SendBuffer.AddRange(crcCode);//CRC校验码


                    var tempSize = Math.Ceiling((double)size / 8);
                    autoResetEvent.Reset();
                    serialPort.ReceivedBytesThreshold = 5 + (int)tempSize;
                    serialPort.Write(m_SendBuffer.ToArray(), 0, m_SendBuffer.Count);

                    if (autoResetEvent.WaitOne(3000) && Check(0x01, 0x01))
                    {
                        byte[] bytes = new byte[(int)tempSize];
                        for (int i = 0; i < tempSize; i++)
                        {
                            bytes[i] = m_RecvBuffer[3 + i];
                        }
                        BitArray bitSet = new BitArray(bytes);
                        var tempBools = new bool[bitSet.Count];
                        bitSet.CopyTo(tempBools, 0);
                        Array.Copy(tempBools, values, size);
                        result = true;
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message, ex);
                }
            }
        }

        public override bool ReadRegister(ushort startAddress, ushort size, out ushort[] values)
        {
            lock (rwLock)
            {
                try
                {
                    bool result = false;
                    values = new ushort[size];
                    m_SendBuffer?.Clear();
                    m_SendBuffer.Add(0x01);//从站地址
                    m_SendBuffer.Add(0x03);//功能码
                    m_SendBuffer.Add(0x00);//起始寄存器高位
                    m_SendBuffer.Add(0x00);//起始寄存器低位

                    byte[] sizeBytes = BitConverter.GetBytes(size);
                    Array.Reverse(sizeBytes);
                    m_SendBuffer.AddRange(sizeBytes);//寄存器数量

                    var crcCode = CRCCode(m_SendBuffer.ToArray());
                    m_SendBuffer.AddRange(crcCode);//CRC校验码

                    autoResetEvent.Reset();
                    serialPort.ReceivedBytesThreshold = 5 + 2 * size;
                    serialPort.Write(m_SendBuffer.ToArray(), 0, m_SendBuffer.Count);

                    if (autoResetEvent.WaitOne(3000) && Check(0x01, 0x03))
                    {
                        byte[] tempBytes = new byte[2 * size];
                        for (int i = 0; i < size; i++)
                        {
                            tempBytes[i * 2] = m_RecvBuffer[3 + i * 2 + 1];
                            tempBytes[i * 2 + 1] = m_RecvBuffer[3 + i * 2];
                        }
                        for (int i = 0; i < size; i++)
                        {
                            values[i] = BitConverter.ToUInt16(tempBytes, i * 2);
                        }
                        result = true;
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message, ex);
                }
            }
        }

        public override bool WriteBit(ushort startAddress, ushort size, bool[] values)
        {
            lock (rwLock)
            {
                try
                {
                    bool result = false;

                    m_SendBuffer?.Clear();

                    var tempSize = Math.Ceiling((double)size / 8);

                    m_SendBuffer.Add(0x01);//从站地址

                    m_SendBuffer.Add(0x0F);//功能码0F写入多个线圈

                    byte[] addressBytes = BitConverter.GetBytes(startAddress);
                    Array.Reverse(addressBytes);
                    m_SendBuffer.AddRange(addressBytes);//起始高低位

                    byte[] sizeBytes = BitConverter.GetBytes(size);
                    Array.Reverse(sizeBytes);
                    m_SendBuffer.AddRange(sizeBytes);//数量高低位

                    m_SendBuffer.Add((byte)(tempSize));//字节数

                    var bA = new BitArray(values);
                    byte[] bytes = new byte[(int)tempSize];
                    bA.CopyTo(bytes, 0);
                    m_SendBuffer.AddRange(bytes);//数据

                    var crcCode = CRCCode(m_SendBuffer.ToArray());
                    m_SendBuffer.AddRange(crcCode);//CRC校验码

                    autoResetEvent.Reset();
                    serialPort.ReceivedBytesThreshold = 8;
                    serialPort.Write(m_SendBuffer.ToArray(), 0, m_SendBuffer.Count);

                    if (autoResetEvent.WaitOne(3000) && Check(0x01, 0x0F))
                    {
                        result = true;
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message, ex);
                }
            }
        }

        public override bool WriteRegister(ushort startAdd, ushort size, ushort[] values)
        {
            lock (rwLock)
            {
                try
                {
                    bool result = false;
                    m_SendBuffer?.Clear();
                    m_SendBuffer.Add(0x01);//从站地址
                    m_SendBuffer.Add(0x10);//功能码

                    byte[] addressBytes = BitConverter.GetBytes(startAdd);
                    Array.Reverse(addressBytes);
                    m_SendBuffer.AddRange(addressBytes);//起始地址高低位

                    byte[] sizeBytes = BitConverter.GetBytes(size);
                    Array.Reverse(sizeBytes);
                    m_SendBuffer.AddRange(sizeBytes);//数量高低位

                    m_SendBuffer.Add((byte)(2 * size));//字节数

                    foreach (var value in values)
                    {
                        byte[] writebytes = BitConverter.GetBytes(value);
                        Array.Reverse(writebytes);
                        m_SendBuffer.AddRange(writebytes);
                    }

                    var crcCode = CRCCode(m_SendBuffer.ToArray());
                    m_SendBuffer.AddRange(crcCode);//CRC校验码

                    autoResetEvent.Reset();
                    serialPort.ReceivedBytesThreshold = 8;
                    serialPort.Write(m_SendBuffer.ToArray(), 0, m_SendBuffer.Count);

                    if (autoResetEvent.WaitOne(3000) && Check(0x01, 0x10))
                    {
                        result = true;
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="byteData"></param>
        /// <returns></returns>
        private byte[] CRCCode(byte[] byteData)
        {
            byte[] CRC = new byte[2];

            ushort wCrc = 0xFFFF;
            for (int i = 0; i < byteData.Length; i++)
            {
                wCrc ^= Convert.ToUInt16(byteData[i]);
                for (int j = 0; j < 8; j++)
                {
                    if ((wCrc & 0x0001) == 1)
                    {
                        wCrc >>= 1;
                        wCrc ^= 0xA001;//异或多项式
                    }
                    else
                    {
                        wCrc >>= 1;
                    }
                }
            }

            CRC[1] = (byte)((wCrc & 0xFF00) >> 8);//高位在后
            CRC[0] = (byte)(wCrc & 0x00FF);       //低位在前
            return CRC;
        }

        private bool Check(byte add, byte funCode)
        {
            try
            {
                return (add == m_RecvBuffer[0]) && (funCode == m_RecvBuffer[1]);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }

    }
    public class RTUParam
    {
        /// <summary>
        /// 端口号
        /// </summary>
        public string PortName = "COM1";

        /// <summary>
        /// 波特率
        /// </summary>
        public int BaudRate = 9600;

        /// <summary>
        /// 数据位
        /// </summary>
        public int DataBit = 8;

        /// <summary>
        /// 停止位
        /// </summary>
        public StopBits StopBit = StopBits.One;

        /// <summary>
        /// 奇偶检验
        /// </summary>
        public Parity Parity = Parity.Odd;
    }
}
